<?php
//
//	file: sys/form.fields.class.php
//	author: Pierre ROGE
//	begin: 05/12/2007
//	version: 0.0.2.CH - 18/08/2008
//	license: http://opensource.org/licenses/gpl-license.php GNU General Public License (GPL)
//

if ( !defined('SYS') )
{
	die('Not allowed');
}

class sys_field extends sys_stdclass
{
	var $name;
	var $type;
	var $output;
	var $value_type;
	var $multi;
	var $value;
	var $channel;
	var $db_field;
	var $data;

	function __construct($api_name)
	{
		parent::__construct($api_name);
		$this->name = false;
		$this->type = false;
		$this->output = false;
		$this->value_type = false;

		$this->multi = false;
		$this->value = false;
		$this->channel = false;
		$this->db_field = false;
		$this->data = false;
	}

	function __destruct()
	{
		unset($this->data);
		unset($this->db_field);
		unset($this->channel);
		unset($this->multi);
		unset($this->value);

		unset($this->value_type);
		unset($this->output);
		unset($this->type);
		unset($this->name);
		parent::__destruct();
	}

	function set($name, $type, $data)
	{
		if ( !($this->name = trim($name)) )
		{
			trigger_error('err_field_name_missing', E_USER_ERROR);
		}
		if ( !($this->type = trim($type)) )
		{
			trigger_error(sys_error::sprintf('err_field_type_missing', $data['name']), E_USER_ERROR);
		}

		// output
		$this->output = false;
		if ( isset($data['output']) )
		{
			$this->output = $data['output'] ? true : false;
			unset($data['output']);
		}

		// input channel
		$this->channel = '_POST';
		if ( isset($data['channel']) )
		{
			$this->channel = $data['channel'];
			unset($data['channel']);
		}

		// database field
		$this->db_field = false;
		if ( isset($data['db.field']) )
		{
			$this->db_field = $data['db.field'] ? $data['db.field'] : false;
			unset($data['db.field']);
		}

		// value
		$this->value = null;
		if ( isset($data['value']) )
		{
			$this->value = $data['value'];
			unset($data['value']);
		}

		// value type
		$this->value_type = null;
		if ( isset($data['value.type']) )
		{
			$this->value_type = $data['value.type'];
			unset($data['value.type']);
		}
		else if ( !is_null($this->value) )
		{
			settype($this->value_type, gettype($this->decode($this->value)));
		}

		// remaining attributes if any
		$this->data = false;
		if ( !empty($data) )
		{
			$this->data = array();
			foreach ( $data as $attr => $def )
			{
				$this->data[ $this->_tpl_varname($attr) ] = $def;
				unset($data[$attr]);
			}
		}
		unset($data);

		// get the value from form if any
		$this->value = $this->get_value();
	}

	// retrieve the value from form if any, else return current one
	function get_value()
	{
		$io = &$GLOBALS[SYS]->io;
		return $this->output || !$io->exists($this->name, $this->channel) ? $this->value : $this->encode($io->read($this->name, $this->value_type, $this->channel));
	}

	// check value
	function check()
	{
		$sys = &$GLOBALS[SYS];
		$error = &$sys->error;

		if ( $this->output )
		{
			return true;
		}
		$cout_warnings = $error->warnings ? count($error->warnings) : 0;
		if ( $this->data )
		{
			if ( isset($this->data['NOT_EMPTY']) && !$this->value )
			{
				$this->trigger_error('err_field_empty');
			}
			else if ( !isset($this->data['EMPTY_ALLOWED']) || $this->value )
			{
				if ( isset($this->data['VALUE_MINI']) && ($this->value < $this->data['VALUE_MINI']) )
				{
					$this->trigger_error('err_field_too_low', $this->data['VALUE_MINI']);
				}
				if ( isset($this->data['VALUE_MAXI']) && ($this->value > $this->data['VALUE_MAXI']) )
				{
					$this->trigger_error('err_field_too_high', $this->data['VALUE_MAXI']);
				}
				if ( isset($this->data['LENGTH_MINI']) && (strlen((string) $this->value) < $this->data['LENGTH_MINI']) )
				{
					$this->trigger_error('err_field_too_tiny', $this->data['LENGTH_MINI']);
				}
				if ( isset($this->data['LENGTH_MAXI']) && (strlen((string) $this->value) > $this->data['LENGTH_MAXI']) )
				{
					$this->trigger_error('err_field_too_wide', $this->data['LENGTH_MAXI']);
				}
			}
		}
		return $error->warnings && (count($error->warnings) > $count_warnings) ? false : true;
	}

	// validation
	function validate()
	{
	}

	// display
	function display($tpl_switch=false)
	{
		$sys = &$GLOBALS[SYS];
		$tpl = &$sys->tpl;

		$switch = $tpl_switch && ($tpl_switch = trim($tpl_switch)) ? $tpl_switch . '.' : '';

		$tpl->add($switch . 'block.line.field', ($this->data ? $this->data : array()) + array(
			'NAME' => $this->name,
			'TYPE' => $this->type,
			'OUTPUT' => $this->output,
			'MULTI' => $this->multi,
			'VALUE' => $this->multi ? false : $this->escape($this->value),
		));
		if ( isset($this->data['JSCRIPT']) && !empty($this->data['JSCRIPT']) )
		{
			$GLOBALS[$this->api_name]->set_resource('jscripts', $this->data['JSCRIPT']);
		}
	}

	// private: encode for memory (applied to the value read from form)
	function encode($value)
	{
		// we accept html
		return $value;
	}

	// private: decode from memory (applied to the memory value to get its form type)
	function decode($value)
	{
		// as we accept html, we don't need to reverse an escape due to encode()
		return $value;
	}

	// private: escape the value for display (applied to the memory value to get its display format)
	function escape($value)
	{
		// we accept html, so we escape it at display time
		return sys::htmlspecialchars($value);
	}

	// private: send a message with the field legend
	function trigger_error()
	{
		$args = func_get_args();
		$msg = array_shift($args);
		array_unshift($args, $msg, isset($this->data['LEGEND']) && $this->data['LEGEND'] ? array($this->data['LEGEND']) : $this->name);
		trigger_error(sys_error::sprintf($args), E_USER_WARNING);
	}

	// private: prepare the tpl varname that will be sent (uppercase, no dot nor space)
	function _tpl_varname($varname)
	{
		return strtoupper(preg_replace('#[^a-z0-9]#i', '_', $varname));
	}
}

// basic def for alpha
class sys_alpha_field extends sys_field
{
	function set($name, $type, $data)
	{
		if ( !isset($data['value']) )
		{
			$data['value'] = '';
		}
		parent::set($name, $type, $data);
	}
}

// basic def for numerics: we don't need to encode the value, as it is casted through the default
class sys_numeric_field extends sys_field
{
	function set($name, $type, $data)
	{
		if ( !isset($data['value']) )
		{
			$data['value'] = 0;
		}
		parent::set($name, $type, $data);
	}
}

// basic def for single option lists: we take the value - at least type - from the list
class sys_list_field extends sys_field
{
	var $options;

	function __construct($api_name)
	{
		parent::__construct($api_name);
		$this->options = false;
	}

	function __destruct()
	{
		unset($this->options);
		parent::__destruct();
	}

	function set($name, $type, $data)
	{
		$this->options = false;

		if ( !isset($data['options']) || !is_array($data['options']) || empty($data['options']) )
		{
			trigger_error(sys_error::sprintf('err_field_options_missing', $name), E_USER_ERROR);
		}
		if ( $name && $type )
		{
			// move $data['options'] to $this->options
			$this->options = array();
			foreach ( $data['options'] as $key => $value )
			{
				// if we have an array indexed by string as each option (eg $options = array($id1 => array('foo' => 'v1', 'bar' => 99, ..), $id2 => ..)
				if ( is_array($value) && !empty($value) && (gettype(key($value)) == gettype('')) )
				{
					foreach ( $value as $sub_key => $sub_value )
					{
						$this->options[$key][ $this->_tpl_varname($sub_key) ] = $sub_value;
					}
				}
				else
				{
					$this->options[$key] = array('LEGEND' => $value);
				}
			}
			unset($data['options']);

			// maybe other complementary def ?
			foreach ( $data as $attr => $def )
			{
				unset($dummy);
				$matches = array();
				if ( preg_match('#^options\.(.*)$#', $attr, $matches) )
				{
					if ( ($attr = trim($matches[1])) && ($attr = strtoupper(str_replace('.', '_', $attr))) && is_array($def) )
					{
						foreach ( $def as $key => $value )
						{
							if ( isset($this->options[$key]) )
							{
								$this->options[$key][$attr] = $value;
							}
						}
					}
					unset($data[$name]);
				}
			}

			// we don't have a value type: get it from the options
			if ( !isset($data['value.type']) )
			{
				// get the first key not disabled
				$found = false;
				foreach ( $this->options as $key => $def )
				{
					if ( ($found = !isset($def['DISABLED'])) )
					{
						$data['value.type'] = null;
						settype($data['value.type'], gettype($key));
						break;
					}
				}

				// all disabled: get the first option to type cast
				if ( !$found )
				{
					reset($this->options);
					$data['value.type'] = null;
					settype($data['value.type'], gettype(key($this->options)));
				}

				// we are on a multi-selection list
				if ( $this->multi )
				{
					$data['value.type'] = array($data['value.type']);
				}
			}

			// check size
			if ( isset($data['size']) )
			{
				$data['size'] = max(1, min($data['size'], $this->options ? count($this->options) : 0));
			}
		}

		// back to the standard init
		parent::set($name, $type, $data);
	}

	function check()
	{
		$sys = &$GLOBALS[SYS];
		$error = &$sys->error;

		$cout_warnings = $error->warnings ? count($error->warnings) : 0;
		if ( parent::check() && $this->options && !$this->multi )
		{
			if ( !isset($this->options[$this->value]) )
			{
				$this->trigger_error('err_field_unknown_option');
			}
			else if ( isset($this->options[$this->value]['DISABLED']) && $this->options[$this->value]['DISABLED'] )
			{
				$this->trigger_error('err_field_disabled_option');
			}
		}
		return $error->warnings && (count($error->warnings) > $count_warnings) ? false : true;
	}

	function display($tpl_switch=false)
	{
		$sys = &$GLOBALS[SYS];
		$tpl = &$sys->tpl;

		$switch = $tpl_switch && ($tpl_switch = trim($tpl_switch)) ? $tpl_switch . '.' : '';

		parent::display($tpl_switch);
		if ( $this->options )
		{
			$values = is_null($this->value) || (is_array($this->value) && !$this->value) ? array() : (is_array($this->value) ? array_flip($this->value) : array($this->value => ''));
			foreach ( $this->options as $key => $def )
			{
				$tpl_data = ($def ? $def : array()) + array(
					'VALUE' => $this->escape($key),
				);
				if ( isset($tpl_data['SELECTED']) )
				{
					unset($tpl_data['SELECTED']);
				}
				if ( isset($values[$key]) )
				{
					$tpl_data['SELECTED'] = true;
				}
				$tpl->add($switch . 'block.line.field.option', $tpl_data);
			}
		}
	}
}

class sys_field_list_multi extends sys_field_list
{
	var $null_allowed;

	function __construct($api_name)
	{
		parent::__construct($api_name);
		$this->null_allowed = false;
	}

	function __destruct()
	{
		unset($this->null_allowed);
		parent::__destruct();
	}

	function set($name, $type, $data)
	{
		$sys = &$GLOBALS[SYS];

		$this->multi = true;
		if ( !isset($data['size']) )
		{
			$data['size'] = count($data['options']);
		}
		$data['size'] = max(1, min($sys->ini_get('ppage', 'form'), $data['size']));
		if ( !isset($data['value']) || !$data['value'] || !is_array($data['value']) )
		{
			$data['value'] = array();
			if ( isset($data['options']) && $data['options'] )
			{
				if ( isset($data['options.selected']) && is_array($data['options.selected']) )
				{
					$data['value'] = $data['options.selected'];
				}
				else
				{
					foreach ( $data['options'] as $id => $def )
					{
						if ( isset($def['selected']) )
						{
							$data['value'][] = $id;
							unset($data['options'][$id]['selected']);
						}
					}
				}
			}
		}
		parent::set($name, $type == 'list_multi' ? 'list' : $type, $data);
	}

	function check()
	{
		$sys = &$GLOBALS[SYS];
		$error = &$sys->error;

		$cout_warnings = $error->warnings ? count($error->warnings) : 0;
		if ( parent::check() && $this->options && $this->value )
		{
			foreach ( $this->value as $idx => $key )
			{
				if ( ($key && !isset($this->options[$key])) || (!$key && !$this->null_allowed) )
				{
					$this->trigger_error('err_field_unknown_option');
				}
				else if ( isset($this->options[$key]) && isset($this->options[$key]['DISABLED']) && $this->options[$key]['DISABLED'] )
				{
					$this->trigger_error('err_field_disabled_option');
				}
			}
		}
		return $error->warnings && (count($error->warnings) > $count_warnings) ? false : true;
	}
}

class sys_field_checkbox extends sys_field_list_multi
{
	function set($name, $type, $data)
	{
		$this->null_allowed = true;
		parent::set($name, $type, $data);
	}
}

class sys_field_varchar extends sys_alpha_field
{
	function set($name, $type, $data)
	{
		if ( !isset($data['length_maxi']) )
		{
			$data['length_maxi'] = 255;
		}
		parent::set($name, $type, $data);
	}
}

class sys_field_text extends sys_field_varchar
{
}

class sys_field_int extends sys_numeric_field
{
}

class sys_field_list extends sys_list_field
{
}

class sys_field_radio_list extends sys_list_field
{
}

class sys_field_tree_list extends sys_list_field
{
	function set($name, $type, $data)
	{
		parent::set($name, $data['output'] ? $type : 'list', $data);
	}
}

class sys_field_button extends sys_field
{
	function check() {}
	function read() {}
}

//
// high level types
//
class sys_field_password extends sys_alpha_field
{
	function set($name, $type, $data)
	{
		// force input to form only
		if ( isset($data['channel']) )
		{
			unset($data['channel']);
		}
		parent::set($name, $type, $data);
	}

	function escape()
	{
		return '';
	}
}

class sys_field_email extends sys_alpha_field
{
	function set($name, $type, $data)
	{
		parent::set($name, 'varchar', $data);
	}

	function check()
	{
		$sys = &$GLOBALS[SYS];
		if ( ($res = parent::check()) && !$this->output && !($res = !trim($this->value) || preg_match('#' . sys::preg_expr('email') . '#i', $this->value)) )
		{
			$this->trigger_error('err_field_email_invalid');
		}
		return $res;
	}
}

?>